/*
Copyright 2021 LuJun

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal 
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is furnished 
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in 
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

/// <reference path="../base/lcc.ts" />
/// <reference path="../utils/js.ts" />
/// <reference path="../log/log-manager.ts" />
/// <reference path="./worker-group.ts" />

namespace lcc {

const TAG = "lcc/worker/worker-manager.ts";

const Fs = require('fs');
const Path = require('path');

/**
 * @zh
 * 工作者类搜索路径，多个用 ; 隔开。
 */
export let WORKER_PATH = ".";

/**
 * @zh
 * 默认实例名称
 */
export let DEFAULT_INSTANCE_NAME = "default";

/**
 * @zh
 * 默认工作分组
 */
export let DEFAULT_WORKER_GROUP = "default";

/**
 * @zh
 * 工作者标识符<br/>
 * 工作者名称和实例名称确定唯一对象<br/>
 */
export interface WorkerIdentifier {
	/**
	 * @zh
	 * 工作者名称<br/>
	 * 也就是工作者类文件名称，不要后缀
	 */
	workerName:string;

	/**
	 * @zh
	 * 工作者实例名称<br/>
	 * 可选，如果一个工作者类存在多个实例时区分。
	 */
	instanceName?:string;
}

/**
 * @zh
 * 工作者元数据<br/>
 * **特别注意:<br/>
 * 工作者标识符和参数列表需要保持一致性，也就是相同的标识符每次创建出的对象必须相同，<br/>
 * 这里的相同对象并不是javascript中的'==='语法，而是逻辑上时相同的对象<br/>
 * **<br/>
 */
export interface WorkerMeta {
	/**
	 * @zh
	 * 工作者标识符
	 */
	id:WorkerIdentifier;

    /**
     * @zh
	 * 参数列表
     * 可选，工作者构造函数参数列表
     */
	args?:any[];
}

/**
 * @zh
 * 制作工作者元数据
 * 
 * @param workerid 工作者标识符。如果是字符串，则表示工作者名称。
 * @param args 用于创建工作者对象的参数列表
 */
export function makeWorkerMeta(workerID:string|WorkerIdentifier, ...args:any[]):WorkerMeta{
	let _workerID:WorkerIdentifier;
	if(typeof workerID == 'string'){
		_workerID = { workerName : workerID };
	}else{
		_workerID = workerID;
	}
	return {
		id : _workerID,
		args : args,
	};
}

/**
 * @zh
 * 通过参数选项返回工作者元数据
 * 
 * @param workerIDorMeta 工作者标识或者工作者元数据。如果为字符串，则表示工作者名称。
 */
export function optionWorkerMeta(workerIDorMeta:string|WorkerIdentifier|WorkerMeta){
	let workerMeta:WorkerMeta;
	if(typeof workerIDorMeta == 'string'){
		workerMeta = {
			id : {
				workerName : workerIDorMeta,
			}
		};
	}else{
		if(workerIDorMeta['id']){
			workerMeta = <WorkerMeta>workerIDorMeta;
		}else{
			workerMeta = {
				id : <WorkerIdentifier>workerIDorMeta
			};
		}
	}
	return workerMeta;
}

/**
 * @zh
 * 加载工作者类
 * 
 * @param workerName 工作者名称
 */
export function requireWorkerClass(workerName:string){
	for(let wpath of WORKER_PATH.split(';')){
		let classPath = Path.join(wpath, `${workerName}.js`);
		if(Fs.existsSync(classPath)){
			return require(classPath).default;
		}
	}
}

/**
 * @zh
 * 调用master线程
 * 
 * @internal
 */
export let __callMaster:(type:string, ...args:any[])=>Promise<any>;

/**
 * @zh
 * 工作者管理器<br/>
 * 主要管理程序使用到的所有工作者（基本逻辑单元）<br/>
 * 工作者都有所属的工作组，工作组和工作者根据调用的顺序设置优先级由高到底<br>
 * 优先级越高的工作组释放时最先释放<br/>
 * 同样，优先级越高的工作者释放时最先释放<br/>
 * 分组默认为[[DEFAULT_WORKER_GROUP]]
 */
class WorkerManager {
    
    /**
     * 工作组优先级计数
     */
	private _groupPriCount:number = 0;

	/**
	 * @zh
	 * 工作者所属组名称映射表
	 */
	private _workerGroupNameMap:{[key:string]:string} = js.createMap();

    /**
     * @zh
     * 工作组映射表
     */
    private _groupMap:{[key:string]:WorkerGroup} = js.createMap();

	/**
	 * @zh
	 * 是否正在释放
	 */
	private _releasing:boolean = false;

	/**
	 * @zh
	 * 更新工作者所属组映射表
	 * 
	 * @param workerNames 工作者名称数组
	 */
	private updateWorkerGroupNameMap(workerNames:string[], groupName:string){
		for(let workerName of workerNames){
			if(this._workerGroupNameMap[workerName] != null){
				return false;
			}
		}
		for(let workerName of workerNames){
			this._workerGroupNameMap[workerName] = groupName;
		}
		return true;
	}

	/**
	 * @zh
	 * 获得工作者所属工作组名称
	 * 
	 * @param workerName 工作者名称
	 */
	public getWorkerGroupName(workerName:string){
		return this._workerGroupNameMap[workerName] || DEFAULT_WORKER_GROUP;
	}
	
    /**
     * @zh
     * 添加普通工作组<br/>
     * 
     * @param groupName - 工作组名称
	 * @param workerNames 工作者列表
     */
    public async addWorkerGroup(groupName:string, ...workerNames:string[]){
		if(!IS_MASTER && groupName != __WORKER_GROUP){
			return <boolean>await __callMaster('addWorkerGroup', groupName, ...workerNames);
		}
		if(this._releasing){
			logMgr.warn(TAG, `addWorkerGroup '${groupName}' manager is releasing`);
			return false;
		}
		
        // 如果工作组存在
		let group = this._groupMap[groupName];
		if(group){
			let gstate = group.getState();
			switch(gstate){
				case GroupState.UNINITIALIZED:
				case GroupState.INITIALIZING:{
					await group.untilState(GroupState.WORKING);
					return true;
				}
				case GroupState.WORKING:{
					return true;
				}
				case GroupState.DESTROYING:{
					await group.untilState(GroupState.DESTROYED);
					break;
				}
				case GroupState.DESTROYED:{
					break;
				}
			}
		}

		// 如果不是默认组
		if(groupName != DEFAULT_WORKER_GROUP){
			if(!this.updateWorkerGroupNameMap(workerNames, groupName)){
				logMgr.error(TAG, `addWorkerGroup '${groupName}' workerNames conflict!`);
				return false;
			}
		}
		
        // 创建新工作组
		group = new WorkerGroup(groupName, ++this._groupPriCount, workerNames);
		this._groupMap[groupName] = group;
		await group.initialize();
		DEBUG.WORKER && logMgr.debug(TAG, `addWorkerGroup '${groupName}' success`);
		return true;
    }
	
    /**
     * @zh
     * 添加线程工作组<br/>
     * 
     * @param groupName - 工作组名称
     * @param envScript - 环境脚本,可以导出,load():Promise<void>和unload():Promise<void>用于创建和销毁
     */
    public async addThreadGroup(groupName:string, envScript:string, ...workerNames:string[]){
		if(!IS_MASTER && groupName != __WORKER_GROUP){
			return <boolean>await __callMaster('addThreadGroup', groupName, envScript);
		}
		if(this._releasing){
			logMgr.warn(TAG, `addThreadGroup '${groupName}' manager is releasing`);
			return false;
		}
		
        // 如果工作组存在
		let group = this._groupMap[groupName];
		if(group){
			let gstate = group.getState();
			switch(gstate){
				case GroupState.UNINITIALIZED:
				case GroupState.INITIALIZING:{
					await group.untilState(GroupState.WORKING);
					return true;
				}
				case GroupState.WORKING:{
					return true;
				}
				case GroupState.DESTROYING:{
					await group.untilState(GroupState.DESTROYED);
					break;
				}
				case GroupState.DESTROYED:{
					break;
				}
			}
		}
		
		// 如果不是默认组
		if(groupName != DEFAULT_WORKER_GROUP){
			if(!this.updateWorkerGroupNameMap(workerNames, groupName)){
				logMgr.error(TAG, `addWorkerGroup '${groupName}' workerNames conflict!`);
				return false;
			}
		}
		
        // 创建新工作组
		group = new ThreadGroup(groupName, ++this._groupPriCount, envScript, workerNames);
		this._groupMap[groupName] = group;
		await group.initialize();
		DEBUG.WORKER && logMgr.debug(TAG, `addThreadGroup '${groupName}' '${envScript}' success`);
		return true;
    }

    /**
     * @zh
     * 添加进程工作组<br/>
     * 
     * @param groupName - 工作组名称
     * @param envScript - 环境脚本,可以导出,load():Promise<void>和unload():Promise<void>用于创建和销毁
     */
    public async addProcessGroup(groupName:string, envScript:string, ...workerNames:string[]){
		if(!IS_MASTER && groupName != __WORKER_GROUP){
			return <boolean>await __callMaster('addProcessGroup', groupName, envScript);
		}
		if(this._releasing){
			logMgr.warn(TAG, `addProcessGroup '${groupName}' manager is releasing`);
			return false;
		}
		
        // 如果工作组存在
		let group = this._groupMap[groupName];
		if(group){
			let gstate = group.getState();
			switch(gstate){
				case GroupState.UNINITIALIZED:
				case GroupState.INITIALIZING:{
					await group.untilState(GroupState.WORKING);
					return true;
				}
				case GroupState.WORKING:{
					return true;
				}
				case GroupState.DESTROYING:{
					await group.untilState(GroupState.DESTROYED);
					break;
				}
				case GroupState.DESTROYED:{
					break;
				}
			}
		}
		
		// 如果不是默认组
		if(groupName != DEFAULT_WORKER_GROUP){
			if(!this.updateWorkerGroupNameMap(workerNames, groupName)){
				logMgr.error(TAG, `addWorkerGroup '${groupName}' workerNames conflict!`);
				return false;
			}
		}
		
        // 创建新工作组
		group = new ProcessGroup(groupName, ++this._groupPriCount, envScript, workerNames);
		this._groupMap[groupName] = group;
		await group.initialize();
		DEBUG.WORKER && logMgr.debug(TAG, `addProcessGroup '${groupName}' '${envScript}' success`);
		return true;
    }

    /**
     * @zh
     * 获得工作组</br>
     * 如果当前不存在，则会创建一个普通工作组
     * 
     * @param groupName - 工作组名称，默认[[DEFAULT_WORKER_GROUP]]
     * @param create - 不存在时，是否创建默认工作组，默认true
     */
    protected async getGroup(groupName:string = DEFAULT_WORKER_GROUP) {
        let group = this._groupMap[groupName];
        if(!group && groupName == DEFAULT_WORKER_GROUP){
            await this.addWorkerGroup(groupName);
			group = this._groupMap[groupName];
        }
        return group;
    }

    /**
     * @zh
     * 启动工作者
     * 
     * @param workerMeta - 工作者元数据
     */
    public async startWorker(workerMeta:WorkerMeta){
		if(IS_MASTER){
			let groupName = this.getWorkerGroupName(workerMeta.id.workerName);
			let group = await this.getGroup(groupName);
			if(group){
				return group.startWorker(workerMeta);
			}else{
				return logMgr.debug(TAG, `startWorker group:'${groupName}' not found`);
			}
		}else{
			let group = await this.getGroup(__WORKER_GROUP);
			if(group.containWorkerName(workerMeta.id.workerName)){
				return group.startWorker(workerMeta);
			}else{
				return <boolean>await __callMaster('startWorker', workerMeta);
			}
		}
    }

    /**
     * @zh
     * 停止工作者
     * 
     * @param workerIDorMeta - 工作者标识或者工作者元数据。如果为字符串，则表示工作者名称，所属工作组名称为默认工作组
     */
    public async stopWorker(workerIDorMeta:string|WorkerIdentifier|WorkerMeta, soft:boolean = false){
		let workerMeta = optionWorkerMeta(workerIDorMeta);
		if(IS_MASTER){
			let groupName = this.getWorkerGroupName(workerMeta.id.workerName);
			let group = await this.getGroup(groupName);
			if(group){
				return await group.stopWorker(workerMeta, soft);
			}
		}else{
			let group = await this.getGroup(__WORKER_GROUP);
			if(group.containWorkerName(workerMeta.id.workerName)){
				return await group.stopWorker(workerMeta, soft);
			}else{
				return <boolean>await __callMaster('stopWorker', workerMeta, soft);
			}
		}
    }

    /**
     * @zh
     * 请求工作者调用
     * 
     * @param workerIDorMeta - 工作者标识或者工作者元数据。如果为字符串，则表示工作者名称，所属工作组名称为默认工作组
     * @param type - 调用类型
     * @param args - 调用参数
     * 
     * @returns 调用返回值
     */
    public async callWorker(workerIDorMeta:string|WorkerIdentifier|WorkerMeta, type:string, ...args:any[]){
		let workerMeta = optionWorkerMeta(workerIDorMeta);
		if(IS_MASTER){
			let groupName = this.getWorkerGroupName(workerMeta.id.workerName);
			let group = await this.getGroup(groupName);
			if(group){
				return await group.callWorker(workerMeta, type, ...args);
			}
		}else{
			let group = await this.getGroup(__WORKER_GROUP);
			if(group.containWorkerName(workerMeta.id.workerName)){
				return await group.callWorker(workerMeta, type, ...args);
			}else{
				return <WorkerCallResult>await __callMaster('callWorker', workerMeta, type, ...args);
			}
		}
    }

	/**
	 * @zh
	 * 通知工作组
	 * 
	 * @param groupName 工作组名称
	 * @param type 调用类型
	 * @param args 调用参数
	 */
	public async notifyGroup(groupName:string, type:string, ...args:any[]){
		if(IS_MASTER || groupName == __WORKER_GROUP){
			let group = await this.getGroup(groupName);
			if(group){
				await group.notifyAll(type, ...args);
			}
		}else{
			await __callMaster('notifyGroup', groupName, type, ...args);
		}
	}

	/**
	 * @zh
	 * 通知所有工作者
	 * 
	 * @param type 调用类型
	 * @param args 调用参数
	 */
	public async notifyAll(type:string, ...args:any[]){
		if(IS_MASTER){
			for(let groupName of Object.keys(this._groupMap)){
				await this.notifyGroup(groupName, type, ...args);
			}
		}else{
			await __callMaster('notifyAll', type, ...args);
		}
	}

    /**
     * @zh
     * 释放当前工作对象<br/>
     */
    public async release(){
        DEBUG.WORKER && logMgr.debug(TAG, `release`);
		if(!IS_MASTER){
			logMgr.warn(TAG, `release must in MASTER`);
			return;
		}
		this._releasing = true;
        let groupNames = Object.values(this._groupMap).
			sort((a,b)=>{ return b.getPriority() - a.getPriority(); }).
			map((v)=>{ return v.getName(); });
        for(let groupName of groupNames){
			let group = this._groupMap[groupName];
			if(group){
				delete this._groupMap[groupName];
				await group.destroy();
			}
        }
		this._workerGroupNameMap = js.createMap();
		this._releasing = false;
    }
}

/**
 * @zh
 * 工作者管理器对象
 */
export const workerMgr = new WorkerManager();

/**
 * @zh
 * 当被Worker调用映射表
 * 
 * @internal
 */
 export let __onWorkerCallMap:{[key:number]:(...args:any[])=>Promise<any>} = js.createMap();

 /**
  * 当被Master调用映射表
  */
 export let __onMasterCallMap:{[key:number]:(...args:any[])=>Promise<any>} = js.createMap();
 
__onWorkerCallMap['addWorkerGroup'] = async (gname:string, ...workerNames:string[])=>{
	return await workerMgr.addWorkerGroup(gname, ...workerNames);
};
__onWorkerCallMap['addThreadGroup'] = async (gname:string, envScript:string, ...workerNames:string[])=>{
	return await workerMgr.addThreadGroup(gname, envScript, ...workerNames);
};
__onWorkerCallMap['addProcessGroup'] = async (gname:string, envScript:string, ...workerNames:string[])=>{
	return await workerMgr.addProcessGroup(gname, envScript, ...workerNames);
};
__onWorkerCallMap['startWorker'] = async (workerMeta:WorkerMeta)=>{
	return await workerMgr.startWorker(workerMeta);
};
__onWorkerCallMap['stopWorker'] = async (workerIDorMeta:string|WorkerIdentifier|WorkerMeta, soft:boolean)=>{
	return await workerMgr.stopWorker(workerIDorMeta,soft);
};
__onWorkerCallMap['callWorker'] = async (workerIDorMeta:string|WorkerIdentifier|WorkerMeta, type:string, ...args:any[])=>{
	return await workerMgr.callWorker(workerIDorMeta, type, ...args);
};
__onWorkerCallMap['notifyGroup'] = async (groupName:string, type:string, ...args:any[])=>{
	return await workerMgr.notifyGroup(groupName, type, ...args);
};
__onWorkerCallMap['notifyAll'] = async (type:string, ...args:any[])=>{
	return await workerMgr.notifyAll(type, ...args);
};

__onMasterCallMap['__initialize__'] = async (groupName:string, envScript:string, workerNames:string[])=>{
	__WORKER_GROUP = groupName;
	await workerMgr.addWorkerGroup(groupName, ...workerNames);
	if(envScript){
		let app = require(envScript);
		if(app && app.load){
			await app.load();
		}
	}
};
__onMasterCallMap['__destroy__'] = async (envScript:string)=>{
	if(envScript){
		let app = require(envScript);
		if(app && app.unload){
			await app.unload();
		}
	}
};
__onMasterCallMap['startWorker'] = async (workerMeta:WorkerMeta)=>{
	return await workerMgr.startWorker(workerMeta);
};
__onMasterCallMap['stopWorker'] = async (workerMeta:WorkerMeta, soft:boolean)=>{
	return await workerMgr.stopWorker(workerMeta, soft);
};
__onMasterCallMap['callWorker'] = async (workerMeta:WorkerMeta, type:string, ...args:any[])=>{
	return await workerMgr.callWorker(workerMeta, type, ...args);
};
__onMasterCallMap['notifyAll'] = async (type:string, ...args:any[])=>{
	return await workerMgr.notifyGroup(__WORKER_GROUP, type, ...args);
};

}
